package org.antlr.intellij.adaptor.parser;

import com.neuronbit.businessflow.intellij.antlr.BpmLexer;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.lang.PsiBuilder;
import com.intellij.lang.PsiParser;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.psi.tree.IElementType;
import org.antlr.intellij.adaptor.lexer.PSITokenSource;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.jetbrains.annotations.NotNull;

/**
 * An adaptor that makes an ANTLR parser look like a PsiParser.
 */
public abstract class ANTLRParserAdaptor implements PsiParser {
    /**
     * The Language.
     */
    protected final Language language;
    /**
     * The Parser.
     */
    protected final Parser parser;

    /**
     * Create a jetbrains adaptor for an ANTLR parser object. When
     * the IDE requests a {@link #parse(IElementType, PsiBuilder)},
     * the token stream will be set on the parser.
     *
     * @param language the language
     * @param parser   the parser
     */
    public ANTLRParserAdaptor(Language language, Parser parser) {
		this.language = language;
		this.parser = parser;
	}

    /**
     * Gets language.
     *
     * @return the language
     */
    public Language getLanguage() {
		return language;
	}

	@NotNull
	@Override
	public ASTNode parse(IElementType root, PsiBuilder builder) {
		ProgressIndicatorProvider.checkCanceled();

//		CharStream input = CharStreams.fromString(String.valueOf(builder.getOriginalText()));
//		BpmLexer lexer = new BpmLexer(input);
//		CommonTokenStream tokens = new CommonTokenStream(lexer);

		TokenSource source = new PSITokenSource(builder);
		TokenStream tokens = new CommonTokenStream(source);
		parser.setTokenStream(tokens);
		parser.setErrorHandler(new ErrorStrategyAdaptor()); // tweaks missing tokens
		parser.removeErrorListeners();
		parser.addErrorListener(new SyntaxErrorListener()); // trap errors
		ParseTree parseTree = null;
		PsiBuilder.Marker rollbackMarker = builder.mark();
		try {
			parseTree = parse(parser, root);
		}
		finally {
			rollbackMarker.rollbackTo();
		}

		// Now convert ANTLR parser tree to PSI tree by mimicking subtree
		// enter/exit with mark/done calls. I *think* this creates their parse
		// tree (AST as they call it) when you call {@link PsiBuilder#getTreeBuilt}
		ANTLRParseTreeToPSIConverter listener = createListener(parser, root, builder);
		PsiBuilder.Marker rootMarker = builder.mark();
		ParseTreeWalker.DEFAULT.walk(listener, parseTree);
		while (!builder.eof()) {
			ProgressIndicatorProvider.checkCanceled();
			builder.advanceLexer();
		}
		// NOTE: parse tree returned from parse will be the
		// usual ANTLR tree ANTLRParseTreeToPSIConverter will
		// convert that to the analogous jetbrains AST nodes
		// When parsing an entire file, the root IElementType
		// will be a IFileElementType.
		//
		// When trying to rename IDs and so on, you get a
		// dummy root and a type arg identifier IElementType.
		// This results in a weird tree that has for example
		// (ID (expr (primary ID))) with the ID IElementType
		// as a subtree root as well as the appropriate leaf
		// all the way at the bottom.  The dummy ID root is a
		// CompositeElement and created by
		// ParserDefinition.createElement() despite having
		// being TokenIElementType.
		rootMarker.done(root);
		return builder.getTreeBuilt(); // calls the ASTFactory.createComposite() etc...
	}

    /**
     * Parse parse tree.
     *
     * @param parser the parser
     * @param root   the root
     * @return the parse tree
     */
    protected abstract ParseTree parse(Parser parser, IElementType root);

    /**
     * Create listener antlr parse tree to psi converter.
     *
     * @param parser  the parser
     * @param root    the root
     * @param builder the builder
     * @return the antlr parse tree to psi converter
     */
    protected ANTLRParseTreeToPSIConverter createListener(Parser parser, IElementType root, PsiBuilder builder) {
		return new ANTLRParseTreeToPSIConverter(language, parser, builder);
	}
}
